//
//  KMStorage.m
//  MengLiao
//
//  Created by xiuxin on 2022/7/15.
//  Copyright © 2022 ZHBen. All rights reserved.
//

#import "KMStorage.h"

@interface KUpdateAction : NSObject

@property (strong,nonatomic) NSArray<KDDUpdate *> * updateList;

@property (copy,nonatomic) void (^completion)(BOOL finished);

@end

@implementation KUpdateAction

+ (KUpdateAction *)actionWithUpdate:(NSArray<KDDUpdate *> *)updateList completion:(void (^)(BOOL finished))completion
{
    KUpdateAction * action = [KUpdateAction new];
    action.updateList = updateList;
    action.completion = completion;
    return action;
}

+ (KUpdateAction *)actionWithUpdate:(KDDUpdate *)update
{
    return [self actionWithUpdate:@[update] completion:nil];
}

@end

@interface KUpdateCommitInfo : NSObject

@property (strong,nonatomic) KDDUpdate * update;

@property (copy,nonatomic) void (^completion)(BOOL finished);

@end

@implementation KUpdateCommitInfo

+ (KUpdateCommitInfo *)instanceWithUpdate:(KDDUpdate *)update completion:(void (^)(BOOL finished))completion
{
    KUpdateCommitInfo * info = [KUpdateCommitInfo new];
    info.update = update;
    info.completion = completion;
    return info;
}

@end

@interface KDDStorage (Private)

- (void)commitUpdate:(KDDUpdate *)update;

@end

@interface KDDUpdateBuilder (Private)

@property (nonatomic) BOOL batchUpdating;

@property (strong,nonatomic) NSMutableArray<KDDUpdate *> * batchUpdateList;

@end

@interface KMStorage()<KDDUpdateHandler>

@property (nonatomic) NSInteger sectionOffset;

@property (strong,nonatomic) NSMutableArray<KMStorage *> * storageList;

@property (strong,nonatomic) NSTimer * waitUpdateTimer;

@property (strong,nonatomic) NSMutableArray<KUpdateCommitInfo *> * updateCommitList;

@property (strong,nonatomic) NSMutableArray<KDDSectionData *> * localData;

@end

@implementation KMStorage

- (instancetype)init
{
    return [self initWithData:@[[KDDSectionData new]]];
}

- (instancetype)initWithData:(NSArray<KDDSectionData *> *)sectionDataList
{
    if (self = [super initWithData:sectionDataList])
    {
        self.storageList = [NSMutableArray array];
        self.localData = [NSMutableArray arrayWithArray:self.data];
        self.updateCommitList = [NSMutableArray array];
    }
    return self;
}

#pragma mark SubStorage
- (void)dataStorage:(KMStorage *)storage willCommitUpdate:(KDDUpdate *)update
{
    objc_setAssociatedObject(update, _cmd, @(storage.data.count), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (void)dataStorage:(KMStorage *)storage didCommitUpdate:(KDDUpdate *)update
{
    NSInteger sectionCountBefore = [objc_getAssociatedObject(update, @selector(dataStorage:willCommitUpdate:)) integerValue];
    [self configSectionOffset:storage.index + 1];
    
    [self copySectionDataForUpdate:update];
    NSArray<KDDUpdate *> * updateArray = [self degradeUpdate:update sectionCount:sectionCountBefore];
    [self applyPhysicalOffset:storage.sectionOffset forUpdateList:updateArray];
    
    if (self.batchUpdating)
    {
        [self.batchUpdateList addObjectsFromArray:updateArray];
    }
    else
    {
        [self didReceiveUpdateAction:[KUpdateAction actionWithUpdate:updateArray completion:nil]];
    }
}

- (void)dataStorage:(id<KDataDrivenStorage>)storage performBatchUpdates:(void (^)(void))updates completion:(void (^)(BOOL finished))completion
{
    [self performBatchUpdates:updates animated:NO completion:completion];
}

- (void)didReceiveUpdate:(KDDUpdate *)update
{
    [self didReceiveUpdateAction:[KUpdateAction actionWithUpdate:update]];
}

- (void)didReceiveBatchUpdateList:(NSArray<KDDUpdate *> *)updateList animated:(BOOL)animated completion:(void (^)(BOOL finished))completion
{
    [self didReceiveUpdateAction:[KUpdateAction actionWithUpdate:updateList completion:completion]];
}

#pragma mark Execute UpdateList

- (void)didReceiveUpdateAction:(KUpdateAction *)updateAction
{
    [self commitUpdateAction:updateAction];
    if (self.minUpdateInterval > 0)
    {
        [self.waitUpdateTimer invalidate];
        self.waitUpdateTimer = [NSTimer timerWithTimeInterval:self.minUpdateInterval target:self selector:@selector(pushCommitList) userInfo:nil repeats:NO];
        [[NSRunLoop mainRunLoop] addTimer:self.waitUpdateTimer forMode:NSRunLoopCommonModes];
    }
    else
    {
        [self pushCommitList];
    }
}

- (void)commitUpdateAction:(KUpdateAction *)action
{
    for (NSInteger index = 0; index < action.updateList.count; index++)
    {
        KDDUpdate * update = action.updateList[index];
        [self commitUpdate:update toData:self.localData];
        
        KUpdateCommitInfo * commitInfo = [KUpdateCommitInfo instanceWithUpdate:update completion:nil];
        if (index == action.updateList.count - 1)
        {
            commitInfo.completion = action.completion;
        }
        [self.updateCommitList addObject:commitInfo];
    }
}

- (void)pushCommitList
{
    NSArray<KUpdateCommitInfo *> * commitInfoList = [NSMutableArray arrayWithArray:self.updateCommitList];
    self.updateCommitList = [NSMutableArray array];
    if (commitInfoList.count == 1 && !self.reloadNextUpdate)
    {
        [self pushCommit:commitInfoList[0]];
        KExecuteBlock(commitInfoList[0].completion,YES);
    }
//    else if (commitInfoList.count <= 3 && [self.updateHandler respondsToSelector:@selector(dataStorage:performBatchUpdates:completion:)] && !self.reloadNextUpdate)
//    {
//        @weakify(self)
//        [self.updateHandler dataStorage:self performBatchUpdates:^{
//            @strongify(self)
//            for (KUpdateCommitInfo * commitInfo in commitInfoList)
//            {
//                [self pushCommit:commitInfo];
//            }
//        } completion:^(BOOL finished) {
//            for (KUpdateCommitInfo * commitInfo in commitInfoList)
//            {
//                KExecuteBlock(commitInfo.completion,YES);
//            }
//        }];
//    }
    else
    {
        self.reloadNextUpdate = NO;
        KDDUpdate * update = [[KDDUpdate alloc] initWithTarget:KDDTargetList updateType:KDDUpdateModify];
        update.modelList = [NSMutableArray arrayWithArray:self.localData];
        [self.updateHandler dataStorage:self willCommitUpdate:update];
        [self commitUpdate:update];
        [self.updateHandler dataStorage:self didCommitUpdate:update];
        for (KUpdateCommitInfo * commitInfo in commitInfoList)
        {
            KExecuteBlock(commitInfo.completion,YES);
        }
    }
}

- (void)pushCommit:(KUpdateCommitInfo *)commitInfo
{
    [self.updateHandler dataStorage:self willCommitUpdate:commitInfo.update];
//    self.sectionCount = commitInfo.sectionCount;
    [self commitUpdate:commitInfo.update];
    [self.updateHandler dataStorage:self didCommitUpdate:commitInfo.update];
}

#pragma mark Private
- (void)configSectionOffset:(NSInteger)fromIndex
{
    KMStorage * frontStore = kObjectFromArray(self.storageList, fromIndex - 1);
    for (NSInteger index = fromIndex; index < self.storageList.count; index++)
    {
        KMStorage * store = self.storageList[index];
        store.index = index;
        store.sectionOffset = frontStore.sectionOffset + frontStore.localData.count;
        frontStore = store;
    }
}

- (void)copySectionDataForUpdate:(KDDUpdate *)update
{
    if (update.modelList.count > 0 && (update.target == KDDTargetList || update.target == KDDTargetSection))
    {
        update.modelList = [KDDSectionData copySectionDataList:update.modelList];
    }
}

- (NSArray<KDDUpdate *> *)degradeUpdate:(KDDUpdate *)update sectionCount:(NSInteger)sectionCount
{
    if (update.target == KDDTargetList)
    {
        if (update.updateType == KDDUpdateDelete || update.updateType == KDDUpdateReload)
        {
            if (sectionCount > 0)
            {
                KDDUpdate * update2 = [[KDDUpdate alloc] initWithTarget:KDDTargetSection updateType:update.updateType];
                [update2.indexSet addIndexesInRange:NSMakeRange(0, sectionCount)];
                return @[update2];
            }
            return @[];
        }
        else
        {
            NSInteger minCount = MIN(sectionCount, update.modelList.count);
            NSInteger maxCount = MAX(sectionCount, update.modelList.count);
            NSRange range1 = NSMakeRange(0, minCount);
            NSRange range2 = NSMakeRange(minCount, maxCount - minCount);
            KDDUpdate * modifyUpdate = [[KDDUpdate alloc] initWithTarget:KDDTargetSection updateType:KDDUpdateModify];
            KDDUpdate * update2 = nil;
            [modifyUpdate.indexSet addIndexesInRange:range1];
            if (update.modelList.count < sectionCount)
            {
                [modifyUpdate.modelList addObjectsFromArray:update.modelList];
                update2 = [[KDDUpdate alloc] initWithTarget:KDDTargetSection updateType:KDDUpdateDelete];
                [update2.indexSet addIndexesInRange:range2];
            }
            else if (update.modelList.count > sectionCount)
            {
                [modifyUpdate.modelList addObjectsFromArray:[update.modelList subarrayWithRange:range1]];
                update2 = [[KDDUpdate alloc] initWithTarget:KDDTargetSection updateType:KDDUpdateInsert];
                [update2.indexSet addIndexesInRange:range2];
                [update2.modelList addObjectsFromArray:[update.modelList subarrayWithRange:range2]];
            }
            else
            {
                [modifyUpdate.modelList addObjectsFromArray:update.modelList];
            }
            NSMutableArray<KDDUpdate *> * updateList = [NSMutableArray arrayWithCapacity:2];
            if (update2 != nil)
            {
                [updateList addObject:update2];
            }
            if (modifyUpdate.indexSet.count > 0)
            {
                [updateList addObject:modifyUpdate];
            }
            return updateList;
        }
    }
    return @[update];
}

- (void)applyPhysicalOffset:(NSInteger)sectionOffset forUpdateList:(NSArray<KDDUpdate *> *)updateList
{
    for (KDDUpdate * update in updateList)
    {
        if (update.target == KDDTargetSection)
        {
            NSMutableIndexSet * indexSet = [NSMutableIndexSet indexSet];
            [update.indexSet enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) {
                [indexSet addIndex:sectionOffset + idx];
            }];
            update.indexSet = indexSet;
        }
        else if ([update isMemberOfClass:[KOSUpdate class]])
        {
            KOSUpdate * osUpdate = (KOSUpdate *)update;
            for (KDDUpdate * object in osUpdate.updateList)
            {
                object.section += sectionOffset;
            }
            for (KDDPosition * position in osUpdate.positionList)
            {
                position.section += sectionOffset;
            }
        }
        else
        {
            update.section += sectionOffset;
        }
    }
}

- (void)performBatchUpdates:(void (^)(void))updates animated:(BOOL)animated completion:(void (^)(BOOL))completion
{
    [super performBatchUpdates:updates animated:NO completion:completion];
}

- (NSRange)sectionRange
{
    return NSMakeRange(self.sectionOffset, self.data.count);
}

- (KDDPosition *)physicalPositionOfModel:(NSObject *)model inSection:(NSInteger)sectionIndex
{
    KDDPosition * position = [self positionOfModel:model inSection:sectionIndex];
    position.section += [self physicalSectionOffset];
    return position;
}

- (NSInteger)physicalSectionOffset
{
    NSInteger sectionOffset = self.sectionOffset;
    KMStorage * updateHandler = (KMStorage *)[self updateHandler];
    while ([updateHandler isKindOfClass:KMStorage.class])
    {
        sectionOffset += updateHandler.sectionOffset;
        updateHandler = (KMStorage *)[updateHandler updateHandler];
    }
    return sectionOffset;
}

- (void)insertStorageList:(NSArray<KMStorage *> *)storageList atIndexes:(NSIndexSet *)indexSet
{
    if (self.storageList.count == 0)
    {
        [self reloadStorageList:storageList];
    }
    else
    {
        [self.storageList insertObjects:storageList atIndexes:indexSet];
        [self configSectionOffset:indexSet.firstIndex];
        NSMutableArray<KDDSectionData *> * sectionDataList = [NSMutableArray array];
        NSMutableIndexSet * sectionIndexSet = [NSMutableIndexSet indexSet];
        for (KMStorage * storage in storageList)
        {
            [sectionDataList addObjectsFromArray:storage.data];
            [sectionIndexSet addIndexesInRange:storage.sectionRange];
        }
        [self insertSections:sectionDataList atIndexes:sectionIndexSet];
        [storageList makeObjectsPerformSelector:@selector(setUpdateHandler:) withObject:self];
    }
}

- (void)deleteStorageListAtIndexes:(NSIndexSet *)indexSet
{
    [[self.storageList objectsAtIndexes:indexSet] makeObjectsPerformSelector:@selector(setUpdateHandler:) withObject:nil];
    NSMutableIndexSet * sectionIndexSet = [NSMutableIndexSet indexSet];
    [indexSet enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) {
        KMStorage * storage = self.storageList[idx];
        [sectionIndexSet addIndexesInRange:storage.sectionRange];
    }];
    [self.storageList removeObjectsAtIndexes:indexSet];
    [self configSectionOffset:indexSet.firstIndex];
    [self deleteSectionsAtIndexes:sectionIndexSet];
}

- (void)modifyStorageList:(NSArray<KMStorage *> *)storageList atIndexes:(NSIndexSet *)indexSet
{
    [[self.storageList objectsAtIndexes:indexSet] makeObjectsPerformSelector:@selector(setUpdateHandler:) withObject:nil];
    [self.storageList replaceObjectsAtIndexes:indexSet withObjects:storageList];
    [self configSectionOffset:indexSet.firstIndex];
    NSMutableArray<KDDSectionData *> * sectionDataList = [NSMutableArray array];
    for (KMStorage * storage in self.storageList)
    {
        [sectionDataList addObjectsFromArray:storage.data];
    }
    [storageList makeObjectsPerformSelector:@selector(setUpdateHandler:) withObject:self];
    [self modifyData:sectionDataList];
}

- (void)reloadStorageList:(NSArray<KMStorage *> *)storageList
{
    [self.storageList makeObjectsPerformSelector:@selector(setUpdateHandler:) withObject:nil];
    self.storageList = [NSMutableArray arrayWithArray:storageList];
    [self configSectionOffset:0];
    NSMutableArray<KDDSectionData *> * sectionDataList = [NSMutableArray array];
    for (KMStorage * storage in self.storageList)
    {
        storage.updateHandler = self;
        [sectionDataList addObjectsFromArray:storage.data];
    }
    [self.storageList makeObjectsPerformSelector:@selector(setUpdateHandler:) withObject:self];
    [self modifyData:sectionDataList];
}

- (BOOL)hasUncommittedUpdate
{
    BOOL uncommitted = self.updateCommitList.count > 0;
    if (!uncommitted)
    {
        for (KMStorage * storage in self.storageList)
        {
            if ([storage hasUncommittedUpdate])
            {
                return YES;
            }
        }
    }
    return uncommitted;
}

- (void)pushUncommittedUpdate
{
    for (KMStorage * storage in self.storageList)
    {
        [storage pushUncommittedUpdate];
    }
    if (self.updateCommitList.count > 0)
    {
        [self.waitUpdateTimer invalidate];
        [self pushCommitList];
    }
}

@end
